package scatter.schedulequartz.rest.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import scatter.common.rest.exception.BusinessException;
import scatter.schedulequartz.pojo.form.*;
import scatter.schedulequartz.pojo.record.form.SchedulerRecordClearForm;
import scatter.schedulequartz.pojo.vo.*;
import scatter.schedulequartz.rest.job.HttpInvokerJob;
import scatter.schedulequartz.rest.job.SpringBeanInvokerJob;
import scatter.schedulequartz.rest.mapstruct.ScheduleMapStruct;
import scatter.schedulequartz.rest.record.service.ISchedulerRecordService;
import scatter.schedulequartz.rest.service.IQuartzJobService;

import java.time.LocalDateTime;
import java.util.*;

import static org.quartz.SimpleScheduleBuilder.simpleSchedule;

/**
 * Created by yangwei
 * Created at 2021/2/2 12:58
 */
@Service
public class IQuartzJobServiceImpl implements IQuartzJobService {


    @Autowired
    private List<Scheduler> schedulers;

    @Autowired
    private ScheduleMapStruct scheduleMapStruct;

    @Autowired
    private ISchedulerRecordService iSchedulerRecordService;

    @Override
    public List<ScheduleVo> schedules() throws SchedulerException {
        List<ScheduleVo> result = new ArrayList<>(schedulers.size());
        for (Scheduler scheduler : schedulers) {
            result.add(scheduleMapStruct.mapScheduler(scheduler));
        }
        return result;
    }

    @Override
    public List<Scheduler> schedules(ScheduleQueryForm scheduleQueryForm) throws SchedulerException {
        List<Scheduler> resultTemp = new ArrayList<>(schedulers.size());
        for (Scheduler scheduler : schedulers) {
            if (StrUtil.isEmpty(scheduleQueryForm.getSchedulerName()) || StrUtil.contains(scheduler.getSchedulerName(),scheduleQueryForm.getSchedulerName())) {
                resultTemp.add(scheduler);
            }
        }
        List<Scheduler> result = new ArrayList<>(resultTemp.size());
        for (Scheduler scheduler : resultTemp) {
            if (StrUtil.isEmpty(scheduleQueryForm.getSchedulerInstanceId()) || StrUtil.contains(scheduler.getSchedulerInstanceId(),scheduleQueryForm.getSchedulerInstanceId())) {
                result.add(scheduler);
            }
        }
        return result;
    }


    @Override
    public boolean addJob(JobCronAddForm addJobForm, Scheduler scheduler) throws ClassNotFoundException, SchedulerException {
        Class<? extends Job> clazz = null;

        Map<String, Object> jobDataMap = new HashMap<>();
        if (JobCronAddForm.JOB_CLASS_TYPE_CUSTOM.equals(addJobForm.getJobClassType())) {
            clazz = (Class<? extends Job>) Class.forName(addJobForm.getJobClassName());
        }else if (JobCronAddForm.JOB_CLASS_TYPE_HTTP.equals(addJobForm.getJobClassType())) {
            clazz = HttpInvokerJob.class;
            jobDataMap.put(HttpInvokerJob.HttpInvokerJobDataMapKeys.httpUrl.name(), addJobForm.getHttpUrl());
            jobDataMap.put(HttpInvokerJob.HttpInvokerJobDataMapKeys.httpMethod.name(), addJobForm.getHttpMethod());
            jobDataMap.put(HttpInvokerJob.HttpInvokerJobDataMapKeys.httpHeaders.name(), addJobForm.getHttpHeaders());
            jobDataMap.put(HttpInvokerJob.HttpInvokerJobDataMapKeys.httpParams.name(), addJobForm.getHttpParams());
        }else if (JobCronAddForm.JOB_CLASS_TYPE_BEAN.equals(addJobForm.getJobClassType())) {
            clazz = SpringBeanInvokerJob.class;
            jobDataMap.put(SpringBeanInvokerJob.SpringBeanInvokerJobDataMapKeys.beanName.name(), addJobForm.getBeanName());
            jobDataMap.put(SpringBeanInvokerJob.SpringBeanInvokerJobDataMapKeys.beanMethodName.name(), addJobForm.getBeanMethodName());
            jobDataMap.put(SpringBeanInvokerJob.SpringBeanInvokerJobDataMapKeys.beanMethodParams.name(), addJobForm.getBeanMethodParams());
        }
        if (clazz == null) {
            throw new BusinessException("目前暂不支持其它类型设置支持的类型");
        }
        //构建job信息
        JobDetail jobDetail = JobBuilder.newJob(clazz)
                .withIdentity(addJobForm.getName(), addJobForm.getGroup()).withDescription(addJobForm.getDescription()).build();

        //表达式调度构建器(即任务执行的时间)
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(addJobForm.getCronExpression());
        //按新的 cronExpression 表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(addJobForm.getName(), addJobForm.getGroup()).withSchedule(scheduleBuilder).withDescription("任务默认新建触发器").build();
        //获得JobDataMap，写入数据
        if (addJobForm.getDataMap() != null) {
            jobDetail.getJobDataMap().putAll(addJobForm.getDataMap());
        }
        jobDetail.getJobDataMap().putAll(jobDataMap);
        // scheduler.scheduleJob 会存储 job 如果 name 和 group 已存在会报异常
        scheduler.scheduleJob(jobDetail, trigger);
        return true;
    }

    @Override
    public boolean copyJob(NameAndGroupForm nameAndGroupForm, Scheduler scheduler) throws ClassNotFoundException, SchedulerException {
        JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(nameAndGroupForm.getName(), nameAndGroupForm.getGroup()));
        JobCronAddForm addJobForm = new JobCronAddForm();
        addJobForm.setSchedulerName(scheduler.getSchedulerName());
        addJobForm.setSchedulerInstanceId(scheduler.getSchedulerInstanceId());

        Class<? extends Job> jobClass = jobDetail.getJobClass();
        if (HttpInvokerJob.class.equals(jobClass)) {
            addJobForm.setJobClassType(JobCronAddForm.JOB_CLASS_TYPE_HTTP);
        }else if (SpringBeanInvokerJob.class.equals(jobClass)) {
            addJobForm.setJobClassType(JobCronAddForm.JOB_CLASS_TYPE_BEAN);
        }else {
            addJobForm.setJobClassType(JobCronAddForm.JOB_CLASS_TYPE_CUSTOM);
        }
        addJobForm.setJobClassName(jobClass.getName());

        JobDataMap jobDataMap = jobDetail.getJobDataMap();

        String httpUrl = Optional.ofNullable(jobDataMap.get(HttpInvokerJob.HttpInvokerJobDataMapKeys.httpUrl.name())).map(Objects::toString).orElse(null);
        String httpMethod = Optional.ofNullable(jobDataMap.get(HttpInvokerJob.HttpInvokerJobDataMapKeys.httpMethod.name())).map(Objects::toString).orElse(null);
        Map<String,String> httpHeaders = ((Map) (jobDataMap.get(HttpInvokerJob.HttpInvokerJobDataMapKeys.httpHeaders.name())));
        Map<String,Object> httpParams = ((Map) (jobDataMap.get(HttpInvokerJob.HttpInvokerJobDataMapKeys.httpParams.name())));


        String beanName = Optional.ofNullable(jobDataMap.get(SpringBeanInvokerJob.SpringBeanInvokerJobDataMapKeys.beanName.name())).map(Objects::toString).orElse(null);
        String beanMethodName = Optional.ofNullable(jobDataMap.get(SpringBeanInvokerJob.SpringBeanInvokerJobDataMapKeys.beanMethodName.name())).map(Objects::toString).orElse(null);
        List beanMethodParams = (List)jobDataMap.get(SpringBeanInvokerJob.SpringBeanInvokerJobDataMapKeys.beanMethodParams.name());

        addJobForm.setHttpUrl(httpUrl);
        addJobForm.setHttpMethod(httpMethod);
        addJobForm.setHttpHeaders(httpHeaders);
        addJobForm.setHttpParams(httpParams);

        addJobForm.setBeanName(beanName);
        addJobForm.setBeanMethodName(beanMethodName);
        addJobForm.setBeanMethodParams(beanMethodParams);

        TriggerKey triggerKey = TriggerKey.triggerKey(jobDetail.getKey().getName(), jobDetail.getKey().getGroup());

        Trigger trigger =  scheduler.getTrigger(triggerKey);
        if( trigger instanceof CronTrigger){
            addJobForm.setCronExpression(((CronTrigger) trigger).getCronExpression());

        }

        addJobForm.setName(jobDetail.getKey().getName() + "copy");
        addJobForm.setGroup(jobDetail.getKey().getGroup() + "copy");
        return addJob(addJobForm,scheduler);
    }

    @Override
    public boolean executeOnce(NameAndGroupForm nameAndGroupForm, Scheduler scheduler) throws SchedulerException {
        JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(nameAndGroupForm.getName(), nameAndGroupForm.getGroup()));
        String stuffix = IdWorker.get32UUID();
        Trigger trigger = TriggerBuilder.newTrigger().startNow()

                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withRepeatCount(0))
                .withIdentity(nameAndGroupForm.getName() + "-" + stuffix, nameAndGroupForm.getGroup() + "-" + stuffix)
                .withDescription("executeOnce")
                .forJob(jobDetail)
                .build();
        scheduler.scheduleJob(trigger);
        return false;
    }

    @Override
    public boolean pauseJob(NameAndGroupForm nameAndGroupForm, Scheduler scheduler) throws SchedulerException {
        scheduler.pauseJob(JobKey.jobKey(nameAndGroupForm.getName(), nameAndGroupForm.getGroup()));
        return true;
    }

    @Override
    public boolean pauseTrigger(NameAndGroupForm nameAndGroupForm, Scheduler scheduler) throws SchedulerException {
        scheduler.pauseTrigger(TriggerKey.triggerKey(nameAndGroupForm.getName(),nameAndGroupForm.getGroup()));
        return true;
    }

    @Override
    public boolean resumeJob(NameAndGroupForm nameAndGroupForm, Scheduler scheduler) throws SchedulerException {
        scheduler.resumeJob(JobKey.jobKey(nameAndGroupForm.getName(), nameAndGroupForm.getGroup()));
        return true;
    }

    @Override
    public boolean resumeTrigger(NameAndGroupForm nameAndGroupForm, Scheduler scheduler) throws SchedulerException {
        scheduler.resumeTrigger(TriggerKey.triggerKey(nameAndGroupForm.getName(),nameAndGroupForm.getGroup()));
        return true;
    }

    @Override
    public boolean updateJob(JobCronUpdateForm updateJobForm, Scheduler scheduler) throws SchedulerException, ClassNotFoundException {

        NameAndGroupForm nameAndGroupForm = new NameAndGroupForm();
        nameAndGroupForm.setName(updateJobForm.getOldName());
        nameAndGroupForm.setGroup(updateJobForm.getOldGroup());
        // 删除job，重新添加
        deleteJob(nameAndGroupForm,scheduler);

        JobCronAddForm addJobForm = ScheduleMapStruct.INSTANCE.mapUpdateToAddForm(updateJobForm);
        return addJob(addJobForm,scheduler);

    }

    @Override
    public boolean deleteJob(NameAndGroupForm nameAndGroupForm, Scheduler scheduler) throws SchedulerException {
        scheduler.pauseTrigger(TriggerKey.triggerKey(nameAndGroupForm.getName(), nameAndGroupForm.getGroup()));
        scheduler.unscheduleJob(TriggerKey.triggerKey(nameAndGroupForm.getName(), nameAndGroupForm.getGroup()));
        scheduler.deleteJob(JobKey.jobKey(nameAndGroupForm.getName(), nameAndGroupForm.getGroup()));

        // 清理对应的日志
        SchedulerRecordClearForm schedulerRecordClearForm = new SchedulerRecordClearForm();
        schedulerRecordClearForm.setName(nameAndGroupForm.getName());
        schedulerRecordClearForm.setGroup(nameAndGroupForm.getGroup());
        schedulerRecordClearForm.setBeforeAt(LocalDateTime.now());
        iSchedulerRecordService.clear(schedulerRecordClearForm);
        return true;
    }

    @Override
    public boolean startSchedule(Scheduler scheduler) throws SchedulerException {
        scheduler.start();
        return true;
    }

    @Override
    public boolean standbySchedule(Scheduler scheduler) throws SchedulerException {
        scheduler.standby();
        return true;
    }

    @Override
    public boolean shutdownSchedule(boolean waitForJobsToComplete, Scheduler scheduler) throws SchedulerException {
        if (!scheduler.isShutdown()) {
            scheduler.shutdown(waitForJobsToComplete);
        }
        return true;
    }

    @Override
    public List<JobDetailVo> getJobs(JobQueryForm jobQueryForm, Scheduler scheduler) throws SchedulerException {
        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        if (StrUtil.isNotEmpty(jobQueryForm.getGroup())) {
            matcher = GroupMatcher.jobGroupContains(jobQueryForm.getGroup());
        }
        List<JobDetailVo> jobDetailVoList = new ArrayList<>();
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
        for (JobKey jobKey : jobKeys) {
            if (StrUtil.isNotEmpty(jobQueryForm.getName()) && !StrUtil.contains(jobKey.getName(),jobQueryForm.getName())) {
                continue;
            }
            jobDetailVoList.add(scheduleMapStruct.mapJobDetail(scheduler.getJobDetail(jobKey)));
        }
        String schedulerName = scheduler.getSchedulerName();
        String schedulerInstanceId = scheduler.getSchedulerInstanceId();
        // 填充 cronExpression
        if (CollectionUtil.isNotEmpty(jobDetailVoList)) {
            for (JobDetailVo jobDetailVo : jobDetailVoList) {
                Trigger trigger = scheduler.getTrigger(TriggerKey.triggerKey(jobDetailVo.getName(), jobDetailVo.getGroup()));
                if (trigger instanceof CronTrigger) {
                    jobDetailVo.setCronExpression(((CronTrigger) trigger).getCronExpression());
                }
                jobDetailVo.setSchedulerName(schedulerName);
                jobDetailVo.setSchedulerInstanceId(schedulerInstanceId);
            }
        }
        return jobDetailVoList;
    }

    @Override
    public List<TriggerVo> getTriggers(TriggerQueryForm triggerQueryForm, Scheduler scheduler) throws SchedulerException {
        GroupMatcher<TriggerKey> matcher = GroupMatcher.anyTriggerGroup();
        if (StrUtil.isNotEmpty(triggerQueryForm.getGroup())) {
            matcher = GroupMatcher.triggerGroupContains(triggerQueryForm.getGroup());
        }
        List<TriggerVo> triggerVoList = new ArrayList<>();
        Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(matcher);
        for (TriggerKey triggerKey : triggerKeys) {
            if (StrUtil.isNotEmpty(triggerQueryForm.getName()) && !StrUtil.contains(triggerKey.getName(),triggerQueryForm.getName())) {
                continue;
            }
            triggerVoList.add(scheduleMapStruct.mapTrigger(scheduler.getTrigger(triggerKey),scheduler));
        }
        return triggerVoList;
    }

}
